package smartgrid.agents.broker;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import smartgrid.agents.AbstractAgent;
import smartgrid.conf.BrokerConf;
import smartgrid.modules.trade.Contract;
import smartgrid.modules.trade.Demand;
import smartgrid.modules.trade.Offer;
import smartgrid.output.AbstractEntity;
import smartgrid.output.ContractOutputEntity;
import smartgrid.output.ContractOutputEntity.Field;
import smartgrid.output.OutputController;
import smartgrid.output.AbstractEntity.EntityType;

public class RandomBrokerAgent extends AbstractAgent implements IBrokerAgent {
	
	private static final Logger		logger	= LoggerFactory
																						.getLogger(RandomBrokerAgent.class);
	
	private int										contractId;
	
	private Map<Integer, Offer>		offers;
	
	private Map<Integer, Demand>	demands;
	
	private List<Contract>				contracts;
	
	
	/**
	 * Constructor
	 * 
	 * @param id
	 *          Broker identification
	 * @param conf
	 *          Broker configuration
	 * @return none
	 */
	public RandomBrokerAgent(Integer id, BrokerConf conf) {
		super(id);
		this.contractId = 0;
		this.offers = new HashMap<Integer, Offer>();
		this.demands = new HashMap<Integer, Demand>();
		this.contracts = new ArrayList<Contract>();
	}
	
	
	@Override
	public void initTimestep(int timestep) {
		this.timestep = timestep;
		this.offers.clear();
		this.demands.clear();
		this.contracts.clear();
	}
	
	
	@Override
	public void addOffer(Offer offer) {
		if ((offer != null) && (!this.offers.containsKey(offer.getProsumer()))) {
			this.offers.put(offer.getProsumer(), offer);
		}
	}
	
	
	@Override
	public Map<Integer, Offer> getOffers() {
		return this.offers;
	}
	
	
	@Override
	public void addDemand(Demand demand) {
		if ((demand != null) && (!this.demands.containsKey(demand.getProsumer()))) {
			this.demands.put(demand.getProsumer(), demand);
		}
	}
	
	
	/**
	 * Implements an algorithm similar to the Stable Marriage Problem (SMP) to
	 * assign Offers and Demands
	 * 
	 * @param none
	 * @return Set of contracts assigning Offers and Demands together
	 */
	@Override
	public List<Contract> auction() {
		Map<Integer, Integer> assigned = new HashMap<Integer, Integer>();
		Map<Integer, Double> assignedQty = new HashMap<Integer, Double>();
		
		List<Demand> freeDemands = new ArrayList<Demand>();
		for(Demand demand : this.demands.values()) {
			if (demand.getStatus().equals(Demand.Status.ACTIVE)) {
				freeDemands.add(demand);
			}
		}
		Collections.shuffle(freeDemands);
		
		List<Integer> rmDemand = new ArrayList<Integer>();
		
		while(!freeDemands.isEmpty()) {
			
			for(Demand demand : freeDemands) {
				Queue<Integer> prefProsumers = demand.getPrefProsumers();
				// Queue<Integer> prefProsumers = new LinkedList<Integer>();
				// prefProsumers.addAll(this.offers.keySet());
				
				boolean assign = false;
				while((!assign) && (!prefProsumers.isEmpty())) {
					int offerId = prefProsumers.poll();
					Offer offer = this.offers.get(offerId);
					
					if ((offer.getStatus().equals(Offer.Status.ACTIVE))
							&& (!offer.getExluded().contains(demand.getProsumer()))
							&& (offer.getQuantity() > demand.getQuantity())) {
						
						// Offer is not assigned
						if (!assigned.containsKey(offerId)) {
							assigned.put(offerId, demand.getProsumer());
							assignedQty.put(offerId, demand.getQuantity());
							assign = true;
						}
					}
				}
				
				if ((assign) || (prefProsumers.isEmpty())) {
					if (!rmDemand.contains(demand.getProsumer())) {
						rmDemand.add(demand.getProsumer());
					}
				}
				
			}
			
			if (!rmDemand.isEmpty()) {
				for(Integer demandId : rmDemand) {
					freeDemands.remove(this.demands.get(demandId));
				}
				rmDemand.clear();
			}
		}
		
		for(Integer id : assigned.keySet()) {
			Contract contract = new Contract(this.contractId++, this.timestep, id,
					assigned.get(id), this.offers.get(id).getQuantity(),
					assignedQty.get(id), this.offers.get(id).getPrice());
			this.contracts.add(contract);
			
			logger.debug(contract.toString());
		}
		
		return this.contracts;
	}
	
	
	@Override
	public void generateOutput() {
		
		for(Contract contract : this.contracts) {
			AbstractEntity entity = (ContractOutputEntity) OutputController
					.getEntity(EntityType.CONTRACT);
			
			entity.setValue(Field.CYCLE.name(), this.timestep);
			entity.setValue(Field.CONTRACT_ID.name(), contract.getId());
			entity.setValue(Field.SUPPLIER.name(), contract.getSupplier());
			entity.setValue(Field.BUYER.name(), contract.getBuyer());
			entity.setValue(Field.OFFERED.name(), contract.getQtyOffered());
			entity.setValue(Field.DEMANDED.name(), contract.getQtyDemanded());
			entity.setValue(Field.SUPPLIED.name(), contract.getQtySupplied());
			entity.setValue(Field.CONSUMED.name(), contract.getQtyConsumed());
			entity.setValue(Field.PRICE_PAID.name(), contract.getTotalPrice());
			
			OutputController.setEntity(EntityType.CONTRACT, entity);
		}
	}
}